package org.uim.cricket.storage.deserialize.impl;

import org.uim.cricket.db.impl.FindPlayerByTitleName;
import org.uim.cricket.entities.MatchPlayer;
import org.uim.cricket.entities.Official;
import org.uim.cricket.entities.Player;
import org.uim.cricket.entities.aggr.SingleInning;
import org.uim.cricket.entities.batting.BatsmanOutDetails;
import org.uim.cricket.entities.batting.BatsmanStatistics;
import org.uim.cricket.entities.batting.BattingInning;
import org.uim.cricket.entities.batting.OutTypes;
import org.uim.cricket.entities.bowling.BowlerStatistics;
import org.uim.cricket.entities.bowling.BowlingInning;
import org.uim.cricket.entities.fielding.FieldingTypes;
import org.uim.cricket.entities.fielding.SingleFielding;
import org.uim.cricket.entities.inning.InningExtras;
import org.uim.cricket.entities.inning.InningTotals;
import org.uim.cricket.entities.match.CricketMatch;
import org.uim.cricket.entities.match.Ground;
import org.uim.cricket.entities.match.MatchDates;
import org.uim.cricket.entities.match.MatchID;
import org.uim.cricket.entities.match.MatchOfficials;
import org.uim.cricket.entities.match.MatchResult;
import org.uim.cricket.entities.match.MatchSpanTypes;
import org.uim.cricket.entities.match.MatchSpans;
import org.uim.cricket.entities.match.ResultType;
import org.uim.cricket.entities.match.ResultWonType;
import org.uim.cricket.entities.match.SeriesPlayed;
import org.uim.cricket.entities.match.Toss;
import org.uim.cricket.entities.match.Venue;
import org.uim.cricket.entities.partnerships.FallOfWicket;
import org.uim.cricket.entities.partnerships.Partnership;
import org.uim.cricket.storage.Storage;
import org.uim.cricket.storage.deserialize.AnalyzerDeserializable;
import org.uim.cricket.storage.deserialize.clean.NormalizeBattingStatistics;
import org.uim.cricket.storage.deserialize.clean.NormalizeInningExtras;
import org.uim.cricket.storage.deserialize.clean.NormalizeInningTotal;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.*;
import java.io.*;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class XMLAnalyzerDeserializer implements AnalyzerDeserializable {

	private String sourceXMLFile = null;
	private CricketMatch match = null;
	private Map<String, List<MatchPlayer>> players = null;
	private ArrayList<SingleInning> sinns = new ArrayList<SingleInning>();
	
	public XMLAnalyzerDeserializer() {
		
	}
	public XMLAnalyzerDeserializer(String theXMLFile) {
		this.sourceXMLFile = theXMLFile;
	}
	
	@Override
	public Storage deserialize() throws Exception {
		if (this.sourceXMLFile == null) {
			throw new Exception("Source XML file can't be NULL!");
		}
		if (!new File(this.sourceXMLFile).exists()) {
			throw new FileNotFoundException("Source file can't be found!. [" + this.sourceXMLFile + "]"); 
		}
		
		XPath xpath = XPathFactory.newInstance().newXPath();
		DocumentBuilder dbuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		Document doc = dbuilder.parse(new File(this.sourceXMLFile));
		
		String expression = "/AnalyzeReport/*";
		Object obj = xpath.evaluate(expression, doc, XPathConstants.NODESET);
		if (obj == null) return null;
		NodeList nodeList = (NodeList) obj;

		match = new CricketMatch();
		players = new HashMap<String, List<MatchPlayer>>();
		int innIndex = 0;
		
		
		for (int i=0;i<nodeList.getLength();i++) {
			Node node = nodeList.item(i);
			
			if (node.getNodeName().equals("Team")) {
				this.parseTeamNode(node);
			} else if (node.getNodeName().equals("PartnershipSet")) {
				if (sinns.size() > 0) {
					if (match.getInnings() != null) {
						for (int j=match.getInnings().length-1;j>=0;j--) {
							sinns.add(0, match.getInnings()[j]);
						}
					} 
					SingleInning[] si = new SingleInning[sinns.size()];
					for (int j=0;j<sinns.size();j++) si[j] = sinns.get(j);
					match.setInnings(si);
				}
				this.parsePShipSetNode(node);
			} else if (node.getNodeName().equals("CricketMatch")) {
				this.parseCricketMatchNode(node);
			} else if (node.getNodeName().equals("Inning")) {
				this.parseSingleInning(node, innIndex);
				innIndex++;
			}
			
		}
		
		
		this.doFinalAnalyze();
		
		Storage st = new Storage();
		st.setMatch(match);
		st.setPlayers(players);
		
		return st;
	}
	
	private void parseSingleInning(Node node, int innIndex) throws Exception {
		if (node == null) return;
		
		SingleInning inn = null;
		boolean fromInside = false;
		if (match.getInnings() == null) {
			inn = new SingleInning();
		} else {
			if (innIndex < match.getInnings().length) {
				inn = match.getInnings()[innIndex];
				fromInside = true;
			} else {
				inn = new SingleInning();
			}
		}

		if (node.hasAttributes()) {
			
			for (int a=0; a<node.getAttributes().getLength(); a++) {
				Node attr = node.getAttributes().item(a);
				if (attr.getNodeName().equals("id")) {
					int id = Integer.parseInt(attr.getNodeValue());
					if (id >= 3) inn.setInningTeamItSelf(2);
					else inn.setInningTeamItSelf(1);
					inn.setInningMatch(id);
				} else if (attr.getNodeName().equals("target")) {
					inn.setTargetChased(Integer.parseInt(attr.getNodeValue()));
				} else if (attr.getNodeName().equals("targetOvers")) {
					inn.setTargetChasedOvers(attr.getNodeValue());
				}
			}
			
		}
		
		
		Node totalN = this.getSingleNode(node, "Total");
		InningTotals totals = new InningTotals();
		totals.setDeclared(false); totals.setMinutes(0); totals.setRuns(0);
		totals.setWickets(0); totals.setOvers("0");
		if (totalN != null && totalN.hasAttributes()) {
			
			totals.setDeclared(false);
			
			for (int a=0; a<totalN.getAttributes().getLength(); a++) {
				Node attr = totalN.getAttributes().item(a);
				if (attr.getNodeName().equals("runs")) {
					totals.setRuns(Integer.parseInt(attr.getNodeValue()));
				} else if (attr.getNodeName().equals("overs")) {
					totals.setOvers(attr.getNodeValue());
				} else if (attr.getNodeName().equals("wickets")) {
					totals.setWickets(Integer.parseInt(attr.getNodeValue()));
				} else if (attr.getNodeName().equals("mins")) {
					totals.setMinutes(Integer.parseInt(attr.getNodeValue()));
				} else if (attr.getNodeName().equals("declared")) {
					if (attr.getNodeValue().equalsIgnoreCase("true"))
					totals.setDeclared(true);
				} 
			}
			
		}
		new NormalizeInningTotal().normalize(totals);
		inn.setTotals(totals);
		
		Node extrasN = this.getSingleNode(node, "Extras");
		InningExtras extras = new InningExtras();
		extras.setBies(0); extras.setLegBies(0); extras.setNoBalls(0);
		extras.setOthers(0); extras.setPanelties(0); extras.setWides(0);
		if (extrasN != null && extrasN.hasAttributes()) {
			
			for (int a=0; a<extrasN.getAttributes().getLength(); a++) {
				Node attr = extrasN.getAttributes().item(a);
				if (attr.getNodeName().equals("bies")) {
					extras.setBies(Integer.parseInt(attr.getNodeValue()));
				} else if (attr.getNodeName().equals("legbies")) {
					extras.setLegBies(Integer.parseInt(attr.getNodeValue()));
				} else if (attr.getNodeName().equals("noballs")) {
					extras.setNoBalls(Integer.parseInt(attr.getNodeValue()));
				} else if (attr.getNodeName().equals("wides")) {
					extras.setWides(Integer.parseInt(attr.getNodeValue()));
				} else if (attr.getNodeName().equals("penalties")) {
					extras.setPanelties(Integer.parseInt(attr.getNodeValue()));
				} else if (attr.getNodeName().equals("others")) {
					extras.setOthers(Integer.parseInt(attr.getNodeValue()));
				} 
			}
			
		}
		new NormalizeInningExtras().normalize(extras);
		inn.setExtras(extras);
		
		NodeList fowlN = this.getListOfNodes(node, "Wickets/Wicket");
		if (fowlN != null) {
			
			ArrayList<FallOfWicket> wkts = new ArrayList<FallOfWicket>();
			
			for (int f=0; f<fowlN.getLength(); f++) {
				Node wkt = fowlN.item(f);
				if (!wkt.hasAttributes()) continue;
				
				FallOfWicket fw = new FallOfWicket();
				fw.setRetiredHurt(false);
				for (int a=0; a<wkt.getAttributes().getLength(); a++) {
					Node attr = wkt.getAttributes().item(a);
					if (attr.getNodeName().equals("no")) {
						fw.setWicket(Integer.parseInt(attr.getNodeValue()));
					} else if (attr.getNodeName().equals("fallruns")) {
						fw.setFallOfRuns(Integer.parseInt(attr.getNodeValue()));
					} else if (attr.getNodeName().equals("fallover")) {
						fw.setFallOfOver(attr.getNodeValue());
					} else if (attr.getNodeName().equals("outbatsman")) {
						Player p = new Player();
						p.setTitleName(attr.getNodeValue());
						fw.setOutPlayer(p);
					} else if (attr.getNodeName().equals("retired")) {
						if (attr.getNodeValue().equalsIgnoreCase("true")) 
							fw.setRetiredHurt(true);
					} 
				}
				
				wkts.add(fw);
			}
			
			inn.setFallWickets(wkts);
		}
		
		if (!fromInside)
			sinns.add(inn);
	}

	private String findPlayerIDFromDB(String titleName, String team) throws Exception {
		String query = "CALL sp_FindPlayerByTitleName('" + titleName + "',";
		if (team == null || team.equalsIgnoreCase("null")) 
			query += "NULL);";
		else 
			query += "'" + team + "');";
		
		FindPlayerByTitleName findp = new FindPlayerByTitleName();
		String id = findp.findPlayerID(query);
		return id;
	}
	
	private void doFinalAnalyze() throws Exception {
		if (match.getDebutPlayers() != null) {
			Player[] dbs = match.getDebutPlayers();
			for (int d=0;d<dbs.length;d++) {
				Player fp = this.findPlayer(dbs[d]); 
				if (fp != null) dbs[d] = fp;
			}
			match.setDebutPlayers(dbs);
		}
		
		if (match.getManOfTheMatch() != null) {
			Player[] dbs = match.getManOfTheMatch();
			for (int d=0;d<dbs.length;d++) {
				Player fp = this.findPlayer(dbs[d]); 
				if (fp != null) dbs[d] = fp;
			}
			match.setManOfTheMatch(dbs);
		}
		
		if (match.getManOfTheSeries() != null) {
			Player[] dbs = match.getManOfTheSeries();
			for (int d=0;d<dbs.length;d++) {
				Player fp = this.findPlayer(dbs[d]); 
				if (fp != null) dbs[d] = fp;
				else {
					try {
						if (dbs[d] != null) {
							String resID = this.findPlayerIDFromDB(dbs[d].getTitleName(), dbs[d].getCountry());
							dbs[d].setId(resID);
						}
					} catch (Exception ex) {
						ex.printStackTrace();
					}
				}
			}
			match.setManOfTheSeries(dbs);
		}
		
		if (match.getInnings() != null) {
			for (int i=0;i<match.getInnings().length; i++) {
				SingleInning inn = match.getInnings()[i];
				if (inn==null) continue;
				
				if (inn.getBowledTeam() == null) {
					inn.setBowledTeam(this.findOppositeTeam(inn.getBattedTeam(), match.getTeam1(), match.getTeam2()));
				}
				
				if (inn.getPartnerships() != null && inn.getPartnerships().length > 0) {
					int total = 0; int cleared = 0;
					
					for (int k=0; k<inn.getPartnerships().length; k++) {
						Partnership p = inn.getPartnerships()[k];
						if (p == null) continue;
						total++;
						if (p.getBalls() <= 0) cleared++;
					}
					
					if (total - 1 == cleared) {
						for (int k=0; k<inn.getPartnerships().length; k++) {
							Partnership p = inn.getPartnerships()[k];
							if (p == null) continue;
							p.setBalls(0);
						}
					}
					
				}
				
				if (inn.getFallWickets() == null) continue;
				
				for (int w=0; w<inn.getFallWickets().size(); w++) {
					FallOfWicket fow = inn.getFallWickets().get(w);
					if (fow == null || fow.getOutPlayer() == null) continue;
					Player fp = this.findPlayer(fow.getOutPlayer());
					if (fp != null) {
						fow.setOutPlayer(fp);
					}
				}
			}
		}
		
	}
	
	private String findOppositeTeam(String searchTeam, String team1, String team2) {
		if (team1 != null && !team1.equalsIgnoreCase(searchTeam)) {
			return team1;
		} 
		if (team2 != null && !team2.equalsIgnoreCase(searchTeam)) {
			return team2;
		}
		return null;
	}
	
	private Player findPlayer(Player p) throws Exception {
		if (p.getCountry() != null && p.getCountry().length() > 0) {
			List<MatchPlayer> playx = this.players.get(p.getCountry());
			
			return this.findPlayer(p, playx);
		} else {
			
			ArrayList<Player> founds = new ArrayList<Player>();
			int count = 0;
			for (String team : this.players.keySet()) {
				
				List<MatchPlayer> players = this.players.get(team);
				Player fp = this.findPlayer(p, players);
				if (fp != null) {
					count++;
					founds.add(fp);
				}
			}
			
			if (count == 0) {
				return null;
			} else if (count == 1) {
				return founds.get(0);
			} else {
				throw new Exception ("Two players with same name is found!");
			}
			
		}
		
	}
	
	private Player findPlayer(Player p, List<MatchPlayer> searchOn) {
		if (searchOn == null) return null;
		for (int i=0;i<searchOn.size();i++) {
			MatchPlayer mp = searchOn.get(i);
			if (mp == null || mp.getPlayer() == null) continue;
			if (p.getId() != null) {
				if (p.getId().equals(mp.getPlayer().getId())) return mp.getPlayer();
			}
			if (p.getTitleName() != null) {
				if (p.getTitleName().equals(mp.getPlayer().getTitleName())) return mp.getPlayer();
			}
		}
		return null;
	}
	
	private void parseCricketMatchNode(Node node) throws Exception {
		if (node == null) return;
		
		match.setDayNight(false);
		
		if (node.hasAttributes()) {
			MatchID mid = new MatchID();
			MatchSpans mspans = new MatchSpans();
			
			for (int i=0; i<node.getAttributes().getLength(); i++) {
				Node attr = node.getAttributes().item(i);
				if (attr.getNodeName().equals("type")) {
					mid.setPrefix(attr.getNodeValue());
				} else if (attr.getNodeName().equals("id")) {
					mid.setInternationalID(attr.getNodeValue());
				} else if (attr.getNodeName().equals("spanType")) {
					mspans.setSpanType(MatchSpanTypes.valueOf(attr.getNodeValue())); 
				} else if (attr.getNodeName().equals("spans")) {
					mspans.setSpans(Integer.parseInt(attr.getNodeValue()));
				} 
			}
			
			match.setId(mid);
			match.setSpans(mspans);
		}
		
		XPath xpath = XPathFactory.newInstance().newXPath();
		Object objs = xpath.evaluate("Teams", node, XPathConstants.NODE);
		if (objs == null) return;
		Node teamNode = (Node) objs;
		match.setTeam1(teamNode.getAttributes().getNamedItem("team1").getNodeValue());
		match.setTeam2(teamNode.getAttributes().getNamedItem("team2").getNodeValue());

		xpath = XPathFactory.newInstance().newXPath();
		objs = xpath.evaluate("Ground", node, XPathConstants.NODE);
		if (objs == null) return;
		Node groundNode = (Node) objs;
		Ground g = new Ground();
		if (groundNode.hasAttributes()) {

			for (int a=0; a<groundNode.getAttributes().getLength(); a++) {
				Node attr = groundNode.getAttributes().item(a);
				if (attr.getNodeName().equals("id")) {
					g.setId(attr.getNodeValue());
				} else if (attr.getNodeName().equals("name")) {
					g.setName(attr.getNodeValue());
				} else if (attr.getNodeName().equals("daynight")) {
					if (attr.getNodeValue().equalsIgnoreCase("true"))
						match.setDayNight(true);
				} else if (attr.getNodeName().equals("link")) {
					g.setGroundLink(attr.getNodeValue());
				} else if (attr.getNodeName().equals("venue")) {
					match.setVenue(Venue.valueOf(attr.getNodeValue()));
				} 
			}

		}
		match.setGround(g);
		
		xpath = XPathFactory.newInstance().newXPath();
		objs = xpath.evaluate("Series", node, XPathConstants.NODE);
		if (objs == null) return;
		Node seriesNode = (Node) objs;
		SeriesPlayed sp = new SeriesPlayed();
		if (seriesNode.hasAttributes()) {

			for (int a=0; a<seriesNode.getAttributes().getLength(); a++) {
				Node attr = seriesNode.getAttributes().item(a);
				if (attr.getNodeName().equals("id")) {
					sp.setId(attr.getNodeValue());
				} else if (attr.getNodeName().equals("name")) {
					sp.setName(attr.getNodeValue());
				} else if (attr.getNodeName().equals("match")) {
					sp.setMatch(attr.getNodeValue());
				} else if (attr.getNodeName().equals("season")) {
					sp.setSeason(attr.getNodeValue());
				} else if (attr.getNodeName().equals("link")) {
					sp.setSeriesLink(attr.getNodeValue());
				} 
			}
			
			sp.setResultAfterThisMatch(seriesNode.getNodeValue());
			
			
		} else {
			sp.setResultAfterThisMatch(seriesNode.getNodeValue());
			match.setSeries(sp);
		}
		
		match.setSeries(sp);
		
		xpath = XPathFactory.newInstance().newXPath();
		objs = xpath.evaluate("Result", node, XPathConstants.NODE);
		if (objs == null) return;
		Node resNode = (Node) objs;
		MatchResult mr = new MatchResult();
		if (seriesNode.hasAttributes()) {

			mr.setByDuckworthLewis(false);
			for (int a=0; a<resNode.getAttributes().getLength(); a++) {
				Node attr = resNode.getAttributes().item(a);
				if (attr.getNodeName().equals("id")) {
					mr.setResultType(ResultType.valueOf(attr.getNodeValue()));
				} else if (attr.getNodeName().equals("winteam")) {
					mr.setWonTeam(attr.getNodeValue());
				} else if (attr.getNodeName().equals("wintype")) {
					mr.setWonType(ResultWonType.valueOf(attr.getNodeValue()));
				} else if (attr.getNodeName().equals("margin")) {
					mr.setMargin(Integer.parseInt(attr.getNodeValue()));
				} else if (attr.getNodeName().equals("remainingBalls")) {
					mr.setRemainingBalls(Integer.parseInt(attr.getNodeValue()));
				} else if (attr.getNodeName().equals("dl")) {
					mr.setByDuckworthLewis(true);
				} 
			}
			
		}
		match.setResult(mr);	
		
		xpath = XPathFactory.newInstance().newXPath();
		objs = xpath.evaluate("Dates/Date", node, XPathConstants.NODESET);
		if (objs == null) return;
		NodeList datesNode = (NodeList) objs;
		MatchDates md = new MatchDates();
		Calendar[] cals = new Calendar[datesNode.getLength()];
		for (int i=0; i<datesNode.getLength(); i++) {
			String dt = datesNode.item(i).getTextContent();
			cals[i] = this.captureDates(dt);
		}
		md.setDates(cals);
		match.setDates(md);
		
		xpath = XPathFactory.newInstance().newXPath();
		objs = xpath.evaluate("Notes/*", node, XPathConstants.NODESET);
		if (objs == null) return;
		NodeList notes = (NodeList) objs;
		this.parseMatchNotes(notes);
		
	}
	
	private void parseMatchNotes(NodeList nodes) throws Exception {
		if (nodes == null) return;
		
		for (int i=0; i<nodes.getLength(); i++) {
			Node node = nodes.item(i);
			
			if (node.getNodeName().equals("Toss") && node.hasAttributes()) {
				Toss toss = new Toss();
				for (int a=0; a<node.getAttributes().getLength(); a++) {
					Node attr = node.getAttributes().item(a);
					if (attr.getNodeName().equals("won")) {
						toss.setTossWon(attr.getNodeValue());
					} else if (attr.getNodeName().equals("fielded")) {
						if (attr.getNodeValue().equalsIgnoreCase("true")) toss.setFielded(true);
					}
				}
				match.setToss(toss);
			} else if (node.getNodeName().equals("ManOfTheMatch")) {
				NodeList ps = this.getListOfNodes(node, "Player");
				if (ps != null) {
					Player[] moms = new Player[ps.getLength()];
					for (int n=0; n<ps.getLength(); n++) {
						Node np = ps.item(n);
						Player mop = new Player();
						mop.setTitleName(np.getAttributes().getNamedItem("name").getNodeValue());
						mop.setCountry(np.getAttributes().getNamedItem("team").getNodeValue());
						moms[n] = mop;
					}
					match.setManOfTheMatch(moms);
				}
			} else if (node.getNodeName().equals("ManOfTheSeries")) {
				NodeList ps = this.getListOfNodes(node, "Player");
				if (ps != null) {
					Player[] moms = new Player[ps.getLength()];
					for (int n=0; n<ps.getLength(); n++) {
						Node np = ps.item(n);
						Player mop = new Player();
						mop.setTitleName(np.getAttributes().getNamedItem("name").getNodeValue());
						mop.setCountry(np.getAttributes().getNamedItem("team").getNodeValue());
						moms[n] = mop;
					}
					match.setManOfTheSeries(moms);
				}
				
			} else if (node.getNodeName().equals("Debuts")) {
				NodeList ps = this.getListOfNodes(node, "Player");
				if (ps != null) {
					Player[] debs = new Player[ps.getLength()];
					for (int n=0; n<ps.getLength(); n++) {
						Node np = ps.item(n);
						Player mop = new Player();
						for (int a=0; a<np.getAttributes().getLength(); a++) {
							Node attr = np.getAttributes().item(a);
							if (attr.getNodeName().equals("name")) {
								mop.setTitleName(attr.getNodeValue());
							} else if (attr.getNodeName().equals("team")) {
								mop.setCountry(attr.getNodeValue());
							} 
						}
						debs[n] = mop;
					}
					match.setDebutPlayers(debs);
				}
				
			} else if (node.getNodeName().equals("Officials")) {
				NodeList ps = this.getListOfNodes(node, "Umpire");
				MatchOfficials moffs = new MatchOfficials();
				
				if (ps != null) {
					Official[] offs = new Official[ps.getLength()];
					for (int n=0; n<ps.getLength(); n++) {
						Node np = ps.item(n);
						Official mop = this.captureOfficial(np);
						offs[n] = mop;
					}
					moffs.setUmpires(offs);
				}
				
				Node tvumpnode = this.getSingleNode(node, "TVUmpire");
				if (tvumpnode != null) {
					Official tvoff = this.captureOfficial(tvumpnode);
					moffs.setTvUmpire(tvoff);
				}
				
				tvumpnode = this.getSingleNode(node, "ReservedUmpire");
				if (tvumpnode != null) {
					Official tvoff = this.captureOfficial(tvumpnode);
					moffs.setReservedUmpire(tvoff);
				}
				
				tvumpnode = this.getSingleNode(node, "Referee");
				if (tvumpnode != null) {
					Official tvoff = this.captureOfficial(tvumpnode);
					moffs.setReferee(tvoff);
				}
				
				match.setOfficials(moffs);
			} 
			
		}
	}
	
	private Official captureOfficial(Node offNode) {
		Official tvoff = new Official();
		for (int a=0; a<offNode.getAttributes().getLength(); a++) {
			Node attr = offNode.getAttributes().item(a);
			if (attr.getNodeName().equals("id")) {
				tvoff.setId(attr.getNodeValue());
			} else if (attr.getNodeName().equals("name")) {
				tvoff.setTitleName(attr.getNodeValue());
			} else if (attr.getNodeName().equals("country")) {
				tvoff.setCountry(attr.getNodeValue());
			} else if (attr.getNodeName().equals("shortname")) {
				tvoff.setShortName(attr.getNodeValue());
			} 
		}
		return tvoff;
	}
	
	private NodeList getListOfNodes(Node root, String byName) throws Exception {
		XPath xpath = XPathFactory.newInstance().newXPath();
		Object objs = xpath.evaluate(byName, root, XPathConstants.NODESET);
		if (objs == null) return null;
		return (NodeList) objs;
	}
	
	private Node getSingleNode(Node root, String byName) throws Exception {
		XPath xpath = XPathFactory.newInstance().newXPath();
		Object objs = xpath.evaluate(byName, root, XPathConstants.NODE);
		if (objs == null) return null;
		return (Node) objs;
	}
	
	private Calendar captureDates(String dateStr) {
		if (dateStr == null) return null;
		String[] parts = dateStr.split("/");
		if (parts.length < 3) return null;
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.DATE, Integer.parseInt(parts[0]));
		cal.set(Calendar.MONTH, Integer.parseInt(parts[1])-1);
		cal.set(Calendar.YEAR, Integer.parseInt(parts[2]));
		return cal;
	}
	
	private void parsePShipSetNode(Node node) throws Exception {
		if (node == null) return;
		
		XPath xpath = XPathFactory.newInstance().newXPath();
		Object objs = xpath.evaluate("PartnershipInn", node, XPathConstants.NODESET);
		if (objs == null) return;
		NodeList pshipinns = (NodeList) objs;
		
		SingleInning[] inns = match.getInnings();
		if (inns == null) {
			inns = new SingleInning[4];
		}
		
		for (int i=0;i<pshipinns.getLength();i++) {
			Node pshipNode = pshipinns.item(i);
			
			int inn = 0;
			String batTeam = null;
			if (pshipNode.hasAttributes()) {
				inn = Integer.parseInt(pshipNode.getAttributes().getNamedItem("inning").getNodeValue());
				batTeam = pshipNode.getAttributes().getNamedItem("team").getNodeValue();
			}

			if (inn == 0) continue;
			if (inns[inn-1] == null) inns[inn-1] = new SingleInning();
			inns[inn-1].setBattedTeam(batTeam);
			
			Object sps = xpath.evaluate("Partnership", pshipNode, XPathConstants.NODESET);
			if (sps == null) continue;
			NodeList singlePships = (NodeList) sps;
			
			List<Partnership> allPships = new ArrayList<Partnership>();
			for (int p=0;p<singlePships.getLength();p++) {
				Node sp = singlePships.item(p);
				
				Partnership tp = this.parsePShipTag(sp);
				if (tp != null) {
					allPships.add(tp);
				}
			}
			
			Partnership[] arraypships = new Partnership[allPships.size()];
			allPships.toArray(arraypships);
			inns[inn-1].setPartnerships(arraypships);
		}
		
	}
	
	private void parseTeamNode(Node node) throws Exception {
		String team = null;
		if (node.hasAttributes()) {
			
			for (int a=0; a<node.getAttributes().getLength(); a++) {
				Node attr = node.getAttributes().item(a);
				if (attr.getNodeName().equals("name")) {
					team = attr.getNodeValue();
				}
			}
		}
		
		if (team == null || team.equalsIgnoreCase("null")) return;
		
		XPath xpath = XPathFactory.newInstance().newXPath();
		Object objs = xpath.evaluate("Player", node, XPathConstants.NODESET);
		if (objs == null) throw new Exception("No players are found!");
		NodeList plays = (NodeList) objs;
		
		List<MatchPlayer> allPlayers = new ArrayList<MatchPlayer>();
		if (players.containsKey(team)) {
			allPlayers = players.get(team);
		}
		
		for (int i=0;i<plays.getLength();i++) {
			Node playerNode = plays.item(i);
			
			MatchPlayer mp = this.parserSinglePlayer(playerNode);
			allPlayers.add(mp);
		}
		
		if (!players.containsKey(team)) {
			players.put(team, allPlayers);
		}
	}
	
	private MatchPlayer parserSinglePlayer(Node node) throws Exception {
		MatchPlayer aPlayer = new MatchPlayer();
		
		if (node.hasAttributes()) {
			Player p = new Player();
			
			p.setWicketKeeper(false); 
			p.setCaptain(false);
			NamedNodeMap nmap = node.getAttributes();
			for (int i=0; i<nmap.getLength(); i++) {
				Node pattr = nmap.item(i);
				if (pattr.getNodeName().equals("id")) {
					p.setId(pattr.getNodeValue());
				} else if (pattr.getNodeName().equals("name")) {
					p.setTitleName(pattr.getNodeValue());
				} else if (pattr.getNodeName().equals("shortname")) {
					p.setShortName(pattr.getNodeValue());
				} else if (pattr.getNodeName().equals("link")) {
					p.setPlayerLink(pattr.getNodeValue());
				} else if (pattr.getNodeName().equals("captain")) {
					p.setCaptain(true);
				} else if (pattr.getNodeName().equals("keeper")) {
					p.setWicketKeeper(true);
				} 
			}
			
			aPlayer.setPlayer(p);
		} else {
			throw new Exception("No player description is found!");
		}
		
		XPath xpath = XPathFactory.newInstance().newXPath();
		Object objs = xpath.evaluate("Batting", node, XPathConstants.NODESET);
		if (objs == null) return aPlayer;
		NodeList battings = (NodeList) objs;
		
		// parsing batting innings for a player...
		for (int i=0;i<battings.getLength();i++) {
			Node batnode = battings.item(i);
			
			BattingInning binn = new BattingInning();
			if (batnode.hasAttributes()) {
				binn.setBattedInning(Integer.parseInt(batnode.getAttributes().getNamedItem("inning").getNodeValue()));
				binn.setBattedPosition(Integer.parseInt(batnode.getAttributes().getNamedItem("position").getNodeValue()));
			}
			
			Object bouttyp = xpath.evaluate("OutDetails", batnode, XPathConstants.NODE);
			BatsmanOutDetails batOutDetail = new BatsmanOutDetails();
			batOutDetail.setOutType(OutTypes.DID_NOT_BAT);
			if (bouttyp != null) {
				Node otype = (Node) bouttyp;
				
				for (int a=0;a<otype.getAttributes().getLength();a++) {
					Node attr = otype.getAttributes().item(a);
					if (attr.getNodeName().equals("outType")) {
						batOutDetail.setOutType(OutTypes.valueOf(attr.getNodeValue()));
					} else if (attr.getNodeName().equals("outWicket")) {
						batOutDetail.setOutAsWicket(Integer.parseInt(attr.getNodeValue()));
					} else if (attr.getNodeName().equals("bowler")) {
						Player obowler = new Player();
						obowler.setId(attr.getNodeValue());
						batOutDetail.setBowler(obowler);
					} else if (attr.getNodeName().equals("fielders")) {
						String[] fsstr = attr.getNodeValue().split("[,]");
						Player[] fs = new Player[fsstr.length];
						boolean[] fskeeps = new boolean[fs.length];
						
						for (int f=0;f<fsstr.length;f++) {
							if (fsstr[f].startsWith("+")) {
								fskeeps[f] = true;
								fsstr[f] = fsstr[f].substring(1);
							}
							fs[f] = new Player();
							fs[f].setId(fsstr[f]);
						}
						
						batOutDetail.setWicketKeeperFielders(fskeeps);
						batOutDetail.setFielders(fs);
					} 
				}

			}
			binn.setOutDetails(batOutDetail);
			
			Object bstat = xpath.evaluate("Stats", batnode, XPathConstants.NODE);
			if (bstat != null) {
				Node batstat = (Node) bstat;
				BatsmanStatistics stats = new BatsmanStatistics();
				
				for (int a=0;a<batstat.getAttributes().getLength();a++) {
					Node attr = batstat.getAttributes().item(a);
					if (attr.getNodeName().equals("runs")) {
						stats.setRuns(Integer.parseInt(attr.getNodeValue()));
					} else if (attr.getNodeName().equals("balls")) {
						stats.setBalls(Integer.parseInt(attr.getNodeValue()));
					} else if (attr.getNodeName().equals("minutes")) {
						stats.setMins(Integer.parseInt(attr.getNodeValue()));
					} else if (attr.getNodeName().equals("fours")) {
						stats.setFours(Integer.parseInt(attr.getNodeValue()));
					} else if (attr.getNodeName().equals("sixes")) {
						stats.setSixes(Integer.parseInt(attr.getNodeValue()));
					}
				}
				
				new NormalizeBattingStatistics().normalize(stats);
				binn.setStatistics(stats);
			}
			
			aPlayer.addBattingInning(binn);
		}
		
		// parsing fielding efforts for a player...
		objs = xpath.evaluate("FieldingEfforts/Fielding", node, XPathConstants.NODESET);
		if (objs != null) {
			NodeList fieldings = (NodeList) objs;
			
			for (int i=0;i<fieldings.getLength();i++) {
				Node fieldNode = fieldings.item(i);
				
				if (fieldNode.hasAttributes()) {
					SingleFielding sf = new SingleFielding();
					sf.setInning(Integer.parseInt(fieldNode.getAttributes().getNamedItem("inning").getNodeValue()));
					Player fbatsman = new Player();
					fbatsman.setId(fieldNode.getAttributes().getNamedItem("batsman").getNodeValue());
					sf.setBatsman(fbatsman);
					sf.setFieldType(FieldingTypes.valueOf(fieldNode.getAttributes().getNamedItem("type").getNodeValue()));
					
					aPlayer.addFieldingEffort(sf);
				}
			}
		}
		
		// parsing bowling innings for a player...
		objs = xpath.evaluate("Bowling", node, XPathConstants.NODESET);
		if (objs != null) {
			NodeList bowlings = (NodeList) objs;
			
			for (int i=0;i<bowlings.getLength();i++) {
				Node bowlnode = bowlings.item(i);
				
				BowlingInning binn = new BowlingInning();
				if (bowlnode.hasAttributes()) {
					binn.setBowlingInning(Integer.parseInt(bowlnode.getAttributes().getNamedItem("inning").getNodeValue()));
					binn.setBowlingPosition(Integer.parseInt(bowlnode.getAttributes().getNamedItem("position").getNodeValue()));
				}
				
				Object bstat = xpath.evaluate("Stats", bowlnode, XPathConstants.NODE);
				if (bstat != null) {
					Node batstat = (Node) bstat;
					BowlerStatistics stats = new BowlerStatistics();
					stats.setMaidnes(0); stats.setWickets(0); stats.setRuns(0);
					
					for (int a=0;a<batstat.getAttributes().getLength();a++) {
						Node attr = batstat.getAttributes().item(a);
						if (attr.getNodeName().equals("runs")) {
							stats.setRuns(Integer.parseInt(attr.getNodeValue()));
						} else if (attr.getNodeName().equals("overs")) {
							stats.setOvers(attr.getNodeValue());
						} else if (attr.getNodeName().equals("maidnes")) {
							stats.setMaidnes(Integer.parseInt(attr.getNodeValue()));
						} else if (attr.getNodeName().equals("wickets")) {
							stats.setWickets(Integer.parseInt(attr.getNodeValue()));
						} else if (attr.getNodeName().equals("noballs")) {
							stats.setNoballs(Integer.parseInt(attr.getNodeValue()));
						} else if (attr.getNodeName().equals("wides")) {
							stats.setWides(Integer.parseInt(attr.getNodeValue()));
						}
					}
					
					binn.setStatistics(stats);
				}
				
				Object vms = xpath.evaluate("WicketsTaken/Victim", bowlnode, XPathConstants.NODESET);
				if (bstat != null) {
					NodeList victims = (NodeList) vms;
					
					ArrayList<BattingInning> bvictims = new ArrayList<BattingInning>();
					for (int n=0;n<victims.getLength();n++) {
						Node vc = victims.item(n);
						BattingInning bv = new BattingInning();
						bv.setBatsman(new Player());
						bv.setOutDetails(new BatsmanOutDetails());
						bv.getOutDetails().setFallofWicket(new FallOfWicket());
						
						if (vc.hasAttributes()) {
							for (int ab=0; ab<vc.getAttributes().getLength(); ab++) {
								Node attr = vc.getAttributes().item(ab);
								if (attr.getNodeName().equals("batterID")) {
									bv.getBatsman().setId(attr.getNodeValue());
								} else if (attr.getNodeName().equals("fallInOver")) {
									bv.getOutDetails().getFallofWicket().setFallOfOver(attr.getNodeValue());
								}
							}
						}
						
						
					}
					
					binn.setOutBatsmen(bvictims);
				}
				
				aPlayer.addBowlingInning(binn);
			} // end of each bowling node
		}
		
		return aPlayer;
	}
	
	private Partnership parsePShipTag(Node node) throws Exception {
		if (node == null) return null;
		if (!node.hasAttributes()) return null;
		Partnership p = new Partnership();
		
		for (int a=0;a<node.getAttributes().getLength();a++) {
			Node attr = node.getAttributes().item(a);
			if (attr.getNodeName().equals("wicket")) {
				p.setWicketNo(Integer.parseInt(attr.getNodeValue()));
			} else if (attr.getNodeName().equals("runs")) {
				p.setRuns(Integer.parseInt(attr.getNodeValue()));
			} else if (attr.getNodeName().equals("balls")) {
				p.setBalls(Integer.parseInt(attr.getNodeValue()));
			} else if (attr.getNodeName().equals("started")) {
				BattingInning binn = new BattingInning();
				binn.setBatsman(new Player());
				binn.getBatsman().setId(attr.getNodeValue());
				p.setStartPlayer(binn);
			} else if (attr.getNodeName().equals("arrived")) {
				BattingInning binn = new BattingInning();
				binn.setBatsman(new Player());
				binn.getBatsman().setId(attr.getNodeValue());
				p.setArrivedPlayer(binn);
			} else if (attr.getNodeName().equals("retired")) {
				if (attr.getNodeValue().equalsIgnoreCase("true")) {
					p.setRetired(true);
				}
			} else if (attr.getNodeName().equals("notout")) {
				if (attr.getNodeValue().equalsIgnoreCase("true")) {
					p.setNotOut(true);
				}
			} else if (attr.getNodeName().equals("out")) {
				BattingInning binn = new BattingInning();
				binn.setBatsman(new Player());
				binn.getBatsman().setId(attr.getNodeValue());
				p.setOutPlayer(binn);
			}
		}
		if (p.getRuns() < 0) p.setRuns(0);
		if (p.getBalls() < 0) p.setBalls(0);
		
		if (p.getArrivedPlayer() != null && p.getOutPlayer() != null) {
			if (p.getArrivedPlayer().getBatsman().getId().equals(p.getOutPlayer().getBatsman().getId())) {
				p.setOutPlayer(p.getArrivedPlayer());
			}
		} else if (p.getStartPlayer() != null && p.getOutPlayer() != null) {
			if (p.getStartPlayer().getBatsman().getId().equals(p.getOutPlayer().getBatsman().getId())) {
				p.setOutPlayer(p.getStartPlayer());
			}
		}
		
		return p;
	}
	
	public void setSourceXMLFile(String sourceXMLFile) {
		this.sourceXMLFile = sourceXMLFile;
	}
	
	public String getSourceXMLFile() {
		return sourceXMLFile;
	}
	
}
